#include "driver.h"

namespace Epsilon3D
{
namespace OpenGL
{
	void
	Driver::EnableState (State name)
	{
		switch (name)
		{
			case Lighting:
				glEnable (GL_LIGHTING);
				break;
			case DepthTest:
				glEnable (GL_DEPTH_TEST);
				break;
			default:
				printf (" >> Enabling Unknown State!\n");
				break;
		}
	}

	void
	Driver::DisableState (State name)
	{
		switch (name)
		{
			case Lighting:
				glDisable (GL_LIGHTING);
				break;
			case DepthTest:
				glDisable (GL_DEPTH_TEST);
				break;
			default:
				printf (" >> Disabling Unknown State!\n");
				break;
		}
	}


	void
	Driver::EnableLight (int iLight)
	{
		glEnable (GL_LIGHT0 + iLight);
		GetLight (iLight)->Enable ();
	}

	void Driver::DisableLight (int iLight)
	{
		glDisable (GL_LIGHT0 + iLight);
		GetLight (iLight)->Disable ();
	}

  const ExtList*
	Driver::getExtensions (void)
	{
		return NULL;
	}

	const Driver::Version*
	Driver::getVersion (void)
	{
		if (version_.major == -1) {
			const char* szVersion = (const char *)glGetString (GL_VERSION);

			char* szVer = strdup (szVersion);
			char* szNumber;

			int  iPart   = 1;
			bool bVendor = false;

			for (szNumber = strtok (szVer, "."); szNumber && (! bVendor); szNumber = strtok (NULL, "."))
			{
				// Check for a space in the version string, that denotes the beginning of
				//  the vendor-specific string.
				for (int i = 0; i < strlen (szNumber); i++)
				{
					if (szNumber [i] == ' ') {
						szNumber [i] = '\0';
						bVendor = true;

		  			strcpy (szVer, szVersion);

						// Search for beginning of Vendor Specific string...
						if ((szNumber = strtok (szVer, " ")) && (szNumber = strtok (NULL, " ")))
							version_.vendor_string = strdup (szNumber);

						break;
					}
				}

				switch (iPart++)
				{
					case 1:
						version_.major = atoi (szNumber);
					  break;
					case 2:
						version_.minor = atoi (szNumber);
					  break;
					case 3:
						version_.release = atoi (szNumber);
					  break;
					default:
					  printf (" >> Invalid version string!  (%s)\n", szVersion);
						break;
				}
			}

			free (szVer);

			printf ( "  >> Version: OpenGL %d.%d.%d (%s)\n", version_.major,
			                                               version_.minor,
			                                             version_.release,
			                                           ( version_.vendor_string ?
																							       version_.vendor_string :
																									   getVendor () ) );
		}

		return &version_;
	}

	const char*
	Driver::getVendor (void) const
	{
		return (const char *)glGetString (GL_VENDOR);
	}

	const char*
	Driver::getDeviceName (void) const
	{
		return (const char *)glGetString (GL_RENDERER);
	}


	void
	Driver::Init (void)
	{
		states_ = new e3dRenderState ();

		getVersion    ();
		getExtensions ();

		//
		// Generic 3D Driver Info (present across all APIs)
		//
		glGetIntegerv (GL_MAX_TEXTURE_SIZE,  &max_tex_size_);
		glGetIntegerv (GL_MAX_TEXTURE_UNITS, &num_tex_samplers_);
		glGetIntegerv (GL_MAX_CLIP_PLANES,   &max_clip_planes_);
		glGetIntegerv (GL_MAX_LIGHTS,        &max_hw_lights_);

		for (int i = 0; i < max_hw_lights_; i++)
		{
			Epsilon3D::Light* light = new Epsilon3D::Light (i);
			lights_.push_back (light);
		}

		// Framebuffer details
		glGetIntegerv (GL_DEPTH_BITS,   &fb_info_.depth_bits_);
		glGetIntegerv (GL_STENCIL_BITS, &fb_info_.stencil_bits_);

		glGetIntegerv (GL_RED_BITS,   &fb_info_.red_bits_);
		glGetIntegerv (GL_GREEN_BITS, &fb_info_.green_bits_);
		glGetIntegerv (GL_BLUE_BITS,  &fb_info_.blue_bits_);
		glGetIntegerv (GL_ALPHA_BITS, &fb_info_.alpha_bits_);

		// Vertex and Index Buffer Info
		glGetIntegerv (GL_MAX_ELEMENTS_VERTICES,  &vb_info_.max_elements_);
		glGetIntegerv (GL_MAX_VERTEX_ATTRIBS_ARB, &vb_info_.max_vtx_attribs_);
		glGetIntegerv (GL_MAX_ELEMENTS_INDICES,   &ib_info_.max_elements_);

		//
		// OpenGL-specific stuff
		//
		getVersion    ();
		getExtensions ();


		// OpenGL Stack Limits
		glGetIntegerv (GL_MAX_MODELVIEW_STACK_DEPTH,  &stack_.max_modelview_);
		glGetIntegerv (GL_MAX_PROJECTION_STACK_DEPTH, &stack_.max_projection_);
		glGetIntegerv (GL_MAX_TEXTURE_STACK_DEPTH,    &stack_.max_tex_);




		printf ( "  >> Maximum Tex. Resolution:  (%d x %d)\n", max_tex_size_,
		                                                        max_tex_size_ );
		printf ( "  >> Number of Texture Units:  %d\n", num_tex_samplers_ );
		
		printf ( "  >> Frame Buffer (Depth, Stencil | Red:Green:Blue:Alpha): "
		                          "(%d, %d | %d:%d:%d:%d)\n",
		              fb_info_.depth_bits_, fb_info_.stencil_bits_,
									  fb_info_.red_bits_,
										  fb_info_.green_bits_,
											  fb_info_.blue_bits_,
												  fb_info_.alpha_bits_ );
	}

};
};

using namespace e3dRenderStates;

e3dRenderState::e3dRenderState (void)
{
	DepthTest  = False;
	MatrixMode = ModelView;
}

void
e3dRenderState::SetMatrixMode (e3dRenderStates::MatrixMode mode)
{
	if (MatrixMode != mode) {
		switch (mode)
		{
			case Projection:
				glMatrixMode (GL_PROJECTION);
				break;
			case Texture:
				glMatrixMode (GL_TEXTURE);
				break;
			case ModelView:
			default:
				glMatrixMode (GL_MODELVIEW);
				break;
		}

		MatrixMode = mode;
	}
}

void
e3dRenderState::LoadIdentity (void)
{
	float fMat[4][4];

	fMat [0][0] = 1.f; fMat [0][1] = 0.f; fMat [0][2] = 0.f; fMat [0][3] = 0.f;
	fMat [1][0] = 0.f; fMat [1][1] = 1.f; fMat [1][2] = 0.f; fMat [1][3] = 0.f;
	fMat [2][0] = 0.f; fMat [2][1] = 0.f; fMat [2][2] = 1.f; fMat [2][3] = 0.f;
	fMat [3][0] = 0.f; fMat [3][1] = 0.f; fMat [3][2] = 0.f; fMat [3][3] = 1.f;

	LoadMatrix ((float *)fMat);
}

void
e3dRenderState::LoadIdentity (e3dRenderStates::MatrixMode mode)
{
  e3dRenderStates::MatrixMode original = MatrixMode;

	SetMatrixMode (mode);
	LoadIdentity  ();
	SetMatrixMode (original);
}

void
e3dRenderState::LoadMatrix (float fMatrix[16])
{
  //
	// Test for Special Cases...
	//
	//   1. Identity Matrix
	//   2. Idendical Matrix
	//

	// Don't feed GL a new matrix to load if it's identical...
	if (fMatrix [ 0] == Matrices [MatrixMode][ 0] &&
	    fMatrix [ 1] == Matrices [MatrixMode][ 1] &&
			fMatrix [ 2] == Matrices [MatrixMode][ 2] &&
			fMatrix [ 3] == Matrices [MatrixMode][ 3] &&
			fMatrix [ 4] == Matrices [MatrixMode][ 4] &&
			fMatrix [ 5] == Matrices [MatrixMode][ 5] &&
			fMatrix [ 6] == Matrices [MatrixMode][ 6] &&
			fMatrix [ 7] == Matrices [MatrixMode][ 7] &&
			fMatrix [ 8] == Matrices [MatrixMode][ 8] &&
			fMatrix [ 9] == Matrices [MatrixMode][ 9] &&
			fMatrix [10] == Matrices [MatrixMode][10] &&
			fMatrix [11] == Matrices [MatrixMode][11] &&
			fMatrix [12] == Matrices [MatrixMode][12] &&
			fMatrix [13] == Matrices [MatrixMode][13] &&
			fMatrix [14] == Matrices [MatrixMode][14] &&
			fMatrix [15] == Matrices [MatrixMode][15]) {
//		printf (" >> Attempt to load identical matrix - optimized away.\n");
		return;
	}

	memcpy (Matrices [MatrixMode], fMatrix, sizeof (float) * 16);

  // Use glLoadIdentity for better performance when passing an identity matrix.
	if (fMatrix [ 0] == 1.0f && fMatrix [ 1] == 0.0f && fMatrix [ 2] == 0.0f && fMatrix [ 3] == 0.0f &&
	    fMatrix [ 4] == 0.0f && fMatrix [ 5] == 1.0f && fMatrix [ 6] == 0.0f && fMatrix [ 7] == 0.0f &&
			fMatrix [ 8] == 0.0f && fMatrix [ 9] == 0.0f && fMatrix [10] == 1.0f && fMatrix [11] == 0.0f &&
			fMatrix [12] == 0.0f && fMatrix [13] == 0.0f && fMatrix [14] == 0.0f && fMatrix [15] == 1.0f) {
//		printf (" >> Identity Matrix Detected!  Calling glLoadIdentity ()!\n");
		glLoadIdentity ();
	} else {
		glLoadMatrixf (fMatrix);
	}
}

void
e3dRenderState::LoadMatrix ( e3dRenderStates::MatrixMode mode,
                             float                       fMatrix [16] )
{
  e3dRenderStates::MatrixMode original = MatrixMode;

	SetMatrixMode (mode);
	LoadMatrix    (fMatrix);
	SetMatrixMode (original);
}
